﻿namespace BathroomLocator.Services.Controllers
{
	using System;
	using System.Globalization;
	using System.Web.Mvc;
	using System.Web.Security;
	using BathroomLocator.Services.Infrastructure;
	using BathroomLocator.Services.MembershipWrappers;
	using BathroomLocator.Services.Models;

	[HandleError]
	public class AccountController : Controller
	{
		public AccountController()
			: this(null, null, new RoleProviderWrapper())
		{
		}

		public AccountController(IFormsAuthentication formsAuth, IMembershipService membershipService, RoleProvider roleProvider)
		{
			this.FormsAuth = formsAuth ?? new FormsAuthenticationService();
			this.MembershipService = membershipService ?? new AccountMembershipService();
			this.RoleProvider = roleProvider ?? new RoleProviderWrapper();
		}

		public IFormsAuthentication FormsAuth { get; private set; }

		public IMembershipService MembershipService { get; private set; }

		public RoleProvider RoleProvider { get; private set; }

		public ActionResult LogOn()
		{
			return this.View(new LogOnModel());
		}

		[AcceptVerbs(HttpVerbs.Post)]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Justification = "Needs to take same parameter type as Controller.Redirect()")]
		[ValidateAntiForgeryToken]
		public ActionResult LogOn(LogOnModel model, string returnUrl)
		{
			if (!this.ValidateLogOn(model))
			{
				return this.View(model);
			}

			this.FormsAuth.SignIn(model.UserName, model.RememberMe);
			if (!String.IsNullOrEmpty(returnUrl))
			{
				return this.Redirect(returnUrl);
			}
			else
			{
				return this.RedirectToAction("Index", "Home");
			}
		}

		public ActionResult LogOff()
		{
			this.FormsAuth.SignOut();

			return this.RedirectToAction("Index", "Home");
		}

		public ActionResult Register()
		{
			this.ViewData["PasswordLength"] = this.MembershipService.MinPasswordLength;

			return this.View(new RegisterModel());
		}

		[AcceptVerbs(HttpVerbs.Post)]
		[ValidateAntiForgeryToken]
		public ActionResult Register(RegisterModel model)
		{
			this.ViewData["PasswordLength"] = this.MembershipService.MinPasswordLength;

			if (this.ValidateRegistration(model))
			{
				// Attempt to register the new user.
				var createStatus = this.MembershipService.CreateUser(model.UserName, model.Password, model.Email);
				if (createStatus == MembershipCreateStatus.Success)
				{
					this.SetUserDefaultPermissions(model.UserName);

					this.FormsAuth.SignIn(model.UserName, false /* createPersistentCookie */);
					return this.RedirectToAction("Index", "Home");
				}
				else
				{
					this.ModelState.AddModelError("_FORM", ErrorCodeToString(createStatus));
				}
			}

			// If we got this far, something failed, redisplay form.
			return this.View();
		}

		public ActionResult Unauthorized()
		{
			return this.View();
		}

		#region Validation Methods

		private static string ErrorCodeToString(MembershipCreateStatus createStatus)
		{
			// See http://msdn.microsoft.com/en-us/library/system.web.security.membershipcreatestatus.aspx for
			// a full list of status codes.
			switch (createStatus)
			{
				case MembershipCreateStatus.DuplicateUserName:
					return "Username already exists. Please enter a different user name.";

				case MembershipCreateStatus.DuplicateEmail:
					return "A username for that e-mail address already exists. Please enter a different e-mail address.";

				case MembershipCreateStatus.InvalidPassword:
					return "The password provided is invalid. Please enter a valid password value.";

				case MembershipCreateStatus.InvalidEmail:
					return "The e-mail address provided is invalid. Please check the value and try again.";

				case MembershipCreateStatus.InvalidAnswer:
					return "The password retrieval answer provided is invalid. Please check the value and try again.";

				case MembershipCreateStatus.InvalidQuestion:
					return "The password retrieval question provided is invalid. Please check the value and try again.";

				case MembershipCreateStatus.InvalidUserName:
					return "The user name provided is invalid. Please check the value and try again.";

				case MembershipCreateStatus.ProviderError:
					return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

				case MembershipCreateStatus.UserRejected:
					return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

				default:
					return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
			}
		}

		private bool ValidateLogOn(LogOnModel model)
		{
			if (string.IsNullOrWhiteSpace(model.UserName))
			{
				this.ModelState.AddModelError("username", "You must specify a username.");
			}

			if (string.IsNullOrEmpty(model.Password))
			{
				this.ModelState.AddModelError("password", "You must specify a password.");
			}

			if (!this.MembershipService.ValidateUser(model.UserName, model.Password))
			{
				this.ModelState.AddModelError("_FORM", "The username or password provided is incorrect.");
			}

			return ModelState.IsValid;
		}

		private bool ValidateRegistration(RegisterModel model)
		{
			if (string.IsNullOrWhiteSpace(model.UserName))
			{
				this.ModelState.AddModelError("username", "You must specify a username.");
			}

			if (string.IsNullOrEmpty(model.Email))
			{
				this.ModelState.AddModelError("email", "You must specify an email address.");
			}

			if (model.Password == null || model.Password.Length < this.MembershipService.MinPasswordLength)
			{
				this.ModelState.AddModelError(
					"password",
					string.Format(CultureInfo.CurrentCulture, "You must specify a password of {0} or more characters.", this.MembershipService.MinPasswordLength));
			}

			if (!string.Equals(model.Password, model.ConfirmPassword, StringComparison.Ordinal))
			{
				this.ModelState.AddModelError("_FORM", "The new password and confirmation password do not match.");
			}

			return this.ModelState.IsValid;
		}

		#endregion

		#region Default Permissions Methods

		private void SetUserDefaultPermissions(string userName)
		{
			string[] users = { userName };

			this.AddPermissionsToTables(users);

			this.AddPermissionsToBlobs(users);

			this.AddTablePermission("SampleData", userName);
		}

		private void AddPermissionsToTables(string[] users)
		{
			string[] tableRoles = { RoleConstants.TablesUsageRole };
			this.RoleProvider.AddUsersToRoles(users, tableRoles);
		}

		private void AddPermissionsToBlobs(string[] users)
		{
			string[] blobsRoles = { RoleConstants.BlobsUsageRole };
			this.RoleProvider.AddUsersToRoles(users, blobsRoles);
		}

		private void AddTablePermission(string table, string user)
		{
			var tableRole = string.Format(CultureInfo.InvariantCulture, "{0}{1}", table, RoleConstants.TableRoleSuffix);
			if (!this.RoleProvider.RoleExists(tableRole))
			{
				this.RoleProvider.CreateRole(tableRole);
			}

			string[] users = { user };
			string[] roles = { tableRole };

			this.RoleProvider.AddUsersToRoles(users, roles);
		}

		#endregion Default Permissions Methods
	}
}